package cmap

import (
	"github.com/pkg/errors"
	"sync/atomic"
)

type BucketStatus uint8
const (
	// BUCKET_STATUS_NORMAL 代表散列桶正常。
	BUCKET_STATUS_NORMAL BucketStatus = 0
	// BUCKET_STATUS_UNDERWEIGHT 代表散列桶过轻。
	BUCKET_STATUS_UNDERWEIGHT BucketStatus = 1
	// BUCKET_STATUS_OVERWEIGHT 代表散列桶过重。
	BUCKET_STATUS_OVERWEIGHT BucketStatus = 2
)
type Redistribute interface {
	UpdateThreshold(total uint64,bucketNum int)
	CheckBucketStatus(bucketSize uint64) BucketStatus
	Redistribe(status BucketStatus,buckets []Bucket)(newBucket []Bucket,chanage bool)
}

type myRedistribute struct {
	loadFactor float64
	upperThreshold uint64
	overweightBucketCount uint64
	emptyBucketCount uint64
	maxBucketSize uint64
	minBucketNum int
}

func (this *myRedistribute)UpdateThreshold(total uint64,bucketNum int)  {
	var avg float64
	avg = float64(total/uint64(bucketNum))
	if avg < 100{
		avg = 100
	}
	atomic.StoreUint64(&this.upperThreshold,uint64(avg*this.loadFactor))
}
func (this *myRedistribute)CheckBucketStatus(bucketSize uint64) BucketStatus  {
	if bucketSize > this.maxBucketSize || bucketSize > atomic.LoadUint64(&this.upperThreshold){
		atomic.AddUint64(&this.overweightBucketCount,1)
		return BUCKET_STATUS_OVERWEIGHT
	}
	if bucketSize==0{
		atomic.AddUint64(&this.emptyBucketCount,1)
		return BUCKET_STATUS_UNDERWEIGHT
	}
	return BUCKET_STATUS_NORMAL
}
func (this *myRedistribute)Redistribe(status BucketStatus,buckets []Bucket)(newBuckets []Bucket,chanage bool)  {
	nowNum := len(buckets)
	newNum := nowNum
	switch status {
	case BUCKET_STATUS_OVERWEIGHT:
		if atomic.LoadUint64(&this.overweightBucketCount)*4 < uint64(nowNum){
			return nil,false
		}
		newNum = newNum << 1
	case BUCKET_STATUS_UNDERWEIGHT:
		if  nowNum < this.minBucketNum*2 || atomic.LoadUint64(&this.emptyBucketCount)*4 <uint64(nowNum){
			return nil,false
		}
		newNum = newNum>>1
	default:
		return nil,false

	}
	var items []Item
	for _,bucket:= range buckets{
		for v := bucket.GetFirstItem();v != nil;v = v.Next(){
			items = append(items,v.Copy())
		}
	}
	newBuckets = make([]Bucket,newNum)
	for i:=0 ;i<newNum;i++{
		newBuckets[i] = NewBucket()
	}
	for _,it := range items{
		index := int(it.Hash()%uint64(newNum))
		b := newBuckets[index]
		b.Set(it)
	}
	atomic.StoreUint64(&this.overweightBucketCount, 0)
	atomic.StoreUint64(&this.emptyBucketCount, 0)
	return newBuckets,true
}
func NewRedistribute(loadFactor float64,maxBucketSize uint64,minBucketNum int)(Redistribute,error){
	if loadFactor <0 || maxBucketSize <0 || minBucketNum<0{
		return nil,errors.New("new Redistribute err params is not zero")
	}
	return &myRedistribute{
		loadFactor:loadFactor,
		maxBucketSize:maxBucketSize,
		minBucketNum:minBucketNum,
	},nil
}